﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using planerka.DataAccess.ViewModels;
using System.Web;
using planerka.Common.Enums;
using planerka.DataAccess.Models;
using System.Web.Mvc;
using NLog;
using planerka.Common;
using System.Xml.Serialization;
using System.IO;
using System.Web.Configuration;
using System.Net;

namespace planerka.DataAccess.DataService
{
    public class TaskService
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        // получить список задач
        public IEnumerable<task> getTasksList(long currentUserID, bool getOnlyMyTasks = false)
        {
            List<task> taskList = new List<task>();
            List<long> ownerProjectListIDs = new List<long>();
            
            _Repository<project> projectRepo = new _Repository<project>();
            _Repository<task> taskRepo = new _Repository<task>();

            // 1 - все задачи в проектах, в которых я - владелец
            ownerProjectListIDs = projectRepo.FindAll(p => p.owner_ID == currentUserID).Select(p => p.ID).ToList();
            taskList.AddRange(taskRepo.FindAll(p => ownerProjectListIDs.Contains(p.project_ID)).ToList());

            // 2 - все задачи, в которых я - исполнитель и заказчик
            if (!getOnlyMyTasks)
                taskList.AddRange(taskRepo.FindAll(p => p.performer_ID == currentUserID || p.project.customer_ID == currentUserID).ToList());

            return taskList.Distinct();
        }

        // чтение фильтра для списка пользователей
        public void loadTasksVMfilter(ref tasksViewModel tVM)
        {
            long currentUser_ID = new UserService().getCurrentUser().ID;

            _Repository<userSetting> userSettingRepo = new _Repository<userSetting>();
            userSetting _userSetting = userSettingRepo.FindAll(p => p.user_ID == currentUser_ID).FirstOrDefault();
            if (_userSetting != null)
            {
                // К оплате Session
                tVM.isToPay_filter = _userSetting.tasks_isToPay_filter;

                // Заказчик
                tVM.customerID_filter = _userSetting.tasks_customerID_filter != null ?
                    (long)_userSetting.tasks_customerID_filter : 0;

                // Администратор
                tVM.ownerID_filter = _userSetting.tasks_ownerID_filter != null ?
                    (long)_userSetting.tasks_ownerID_filter : 0;

                // Исполнитель
                tVM.performerID_filter = _userSetting.tasks_performerID_filter != null ?
                    (long)_userSetting.tasks_performerID_filter : 0;

                // Проект
                tVM.projectID_filter = _userSetting.tasks_projectID_filter != null ?
                    (long)_userSetting.tasks_projectID_filter : 0;

                // Статус задачи
                tVM.taskStatus_filter = _userSetting.tasks_status_filter != null ?
                    _userSetting.tasks_status_filter.Split(',').Select(p => Int32.Parse(p)).ToList() : new List<int> 
                { 
                    taskStatuses.Новая.GetHashCode(), 
                    taskStatuses.Выполняется.GetHashCode(),
                    taskStatuses.Выполнена.GetHashCode(),
                    taskStatuses.Отмена.GetHashCode()
                };

                // Приоритет задачи
                tVM.taskPriority_filter = _userSetting.tasks_priority_filter != null ?
                    _userSetting.tasks_priority_filter.Split(',').Select(p => Int32.Parse(p)).ToList() : new List<int> 
                { 
                    priorities.Низкий.GetHashCode(), 
                    priorities.Средний.GetHashCode(),
                    priorities.Высокий.GetHashCode(),
                    priorities.Критичный.GetHashCode()
                };


                // План старт "С:"
                tVM.planStartFilterFrom = _userSetting.tasks_planStartFilterFrom;
                
                // План старт "По:"
                tVM.planStartFilterTo = _userSetting.tasks_planStartFilterTo;
                
            }

        }

        // загрузка модели
        public void fillTasksViewModel(ref tasksViewModel tVM)
        {
            IQueryable<task> tasks;

            long currentUserID = new UserService().getCurrentUser().ID;
            tasks = getTasksList(currentUserID).AsQueryable();
            var tasksCopy = tasks; // для включения задач с платежами внутри диапазона дат

            #region отображение колонок

            tVM.tuneTableColumns_project = true;
            tVM.tuneTableColumns_roles = true;
            tVM.tuneTableColumns_status = true;
            tVM.tuneTableColumns_priority = true;
            tVM.tuneTableColumns_dates = true;
            tVM.tuneTableColumns_sums = true;

            userSetting _userSetting = new _Repository<userSetting>().FindAll(p => p.user_ID == currentUserID).FirstOrDefault();
            if (_userSetting != null)
            {
                tVM.tuneTableColumns_project = _userSetting.viewTableTasks_project;
                tVM.tuneTableColumns_roles = _userSetting.viewTableTasks_roles;
                tVM.tuneTableColumns_status = _userSetting.viewTableTasks_status;
                tVM.tuneTableColumns_priority = _userSetting.viewTableTasks_priority;
                tVM.tuneTableColumns_dates = _userSetting.viewTableTasks_dates;
                tVM.tuneTableColumns_sums = _userSetting.viewTableTasks_sums;
            }


            #endregion

            #region фильтр по администраторам и исполнителям

            // Список администраторов для выпадающего списка
            List<long> tempIDs = tasks.Select(p => p.project.owner_ID).Distinct().ToList();

            using (_Repository<user> userRepo = new _Repository<user>())
            {
                tVM.owners = userRepo.FindAll(p => tempIDs.Contains(p.ID))
                    .OrderBy(p => p.surName).Select(p => new SelectListItem { Text = p.nameView, Value = p.ID.ToString() }).ToList();

                tVM.owners.Insert(0, new SelectListItem { Text = "", Value = "0" });

                // Список заказчиков для выпадающего списка
                tempIDs = tasks.Where(p => p.project.customer_ID != null)
                    .Select(p => (long)p.project.customer_ID).Distinct().ToList();

                tVM.customers = userRepo.FindAll(p => tempIDs.Contains(p.ID))
                    .OrderBy(p => p.surName).Select(p => new SelectListItem { Text = p.nameView, Value = p.ID.ToString() }).ToList();

                tVM.customers.Insert(0, new SelectListItem { Text = "", Value = "0" });

                // Список исполнителей для выпадающего списка
                tempIDs = tasks.Where(p => p.performer_ID != null)
                    .Select(p => (long)p.performer_ID).Distinct().ToList();

                tVM.performers = userRepo.FindAll(p => tempIDs.Contains(p.ID))
                    .OrderBy(p => p.surName).Select(p => new SelectListItem { Text = p.nameView, Value = p.ID.ToString() }).ToList();
            }

            tVM.performers.Insert(0, new SelectListItem { Text = "", Value = "0" });

            #endregion

            #region Список проектов для выпадающего списка

            tempIDs = tasks.Select(p => p.project_ID).Distinct().ToList();
            
            using (_Repository<project> projectRepo = new _Repository<project>())
            {
                tVM.projects = projectRepo.FindAll(p => tempIDs.Contains(p.ID))
                    .OrderBy(p => p.projectName).Select(p => new SelectListItem { Text = p.projectName, Value = p.ID.ToString() }).ToList();
            }

            tVM.projects.Insert(0, new SelectListItem { Text = "", Value = "0" });

            #endregion

            // фильтр по заказчику
            if (tVM.customerID_filter != 0)
            {
                long user_ID = tVM.customerID_filter;

                // включаем фильтр
                tasks = tasks.Where(p => p.project.customer_ID == user_ID);
            }

            // фильтр по администратору
            if (tVM.ownerID_filter != 0)
            {
                long user_ID = tVM.ownerID_filter;

                // включаем фильтр
                tasks = tasks.Where(p => p.project.owner_ID == user_ID).AsQueryable();
            }

            // фильтр по исполнителю
            if (tVM.performerID_filter != 0)
            {
                long user_ID = tVM.performerID_filter;

                // включаем фильтр
                tasks = tasks.Where(p => p.performer_ID == user_ID);
            }

            // фильтр по статусу
            List<int> status_filter = tVM.taskStatus_filter;
            tasks = tasks.Where(p => status_filter.Contains(p.taskStatus));

            // фильтр по приоритету
            List<int> status_priority = tVM.taskPriority_filter;
            tasks = tasks.Where(p => status_priority.Contains(p.priority));
            // выделим периодические задачи, чтобы их в итоге добавить после фильтра дат
            List<task> periodicalTasks = tasks.Where(p => p.taskStatus == taskStatuses.Периодическая.GetHashCode()).ToList();
            // из основного запроса уберем периодические, если есть
            tasks = tasks.Where(p => p.taskStatus != taskStatuses.Периодическая.GetHashCode());

            // фильтр по проекту
            if (tVM.projectID_filter != 0)
            {
                long project_ID = tVM.projectID_filter;
                tasks = tasks.Where(p => p.project_ID == project_ID);
            }

            var tasksWithoutDateFilter = tasks;

            // фильтр по датам план начала и план финиша
            if (tVM.planStartFilterFrom != null)
            {
                DateTime planStartFrom = (DateTime)tVM.planStartFilterFrom;
                tasks = tasks.Where(p => p.planStart >= planStartFrom);
            }

            if (tVM.planStartFilterTo != null)
            {
                DateTime planStartTo = tools.getDayEndForDateTime((DateTime)tVM.planStartFilterTo);
                tasks = tasks.Where(p => p.planStart < planStartTo);
            }

            // фильтр "к оплате" заполняется после заполнения всех платежей по задачам,
            // смотри далее ...

            tVM.tasks = tasks.ToList();

            if (tVM.tasks.Count > 1000)
            {
                tVM.resultMessage = "Количество задач, возвращаемое запросом более 1000. Показаны первые 200 задач. Уточните параметры запроса.";
                tVM.tasks = tVM.tasks.Take(200).ToList();
            }

            // получить платежи администратора(!)

            #region
            // 1 выбрать все задачи без фильтра по датам, у которых есть платежи
            // 2 расширить диапазон, включив задачи с платежами
            using (_Repository<payment> paymentRepo = new _Repository<payment>())
            {
                List<long> taskWithoutDateFilterAndHavePayments_IDs = tasksWithoutDateFilter.Select(p => p.ID).ToList();
                // отберем ID задач, которые имеют платежи в обозначенный период
                var paymentsQuery = paymentRepo
                        .FindAll(p => p.task.project.owner_ID == currentUserID
                            &&  p.task.taskStatus != taskStatuses.Периодическая.GetHashCode()
                            && taskWithoutDateFilterAndHavePayments_IDs.Contains(p.task_ID));

                #region по датам отберем

                if (tVM.planStartFilterFrom != null)
                {
                    DateTime planStartFrom = (DateTime)tVM.planStartFilterFrom;
                    paymentsQuery = paymentsQuery.Where(p => p.timestamp > planStartFrom);
                }

                if (tVM.planStartFilterTo != null)
                {
                    DateTime planStartTo = tools.getDayEndForDateTime((DateTime)tVM.planStartFilterTo);
                    paymentsQuery = paymentsQuery.Where(p => p.timestamp < planStartTo);
                }

                taskWithoutDateFilterAndHavePayments_IDs = paymentsQuery.Select(p => p.task_ID).Distinct().ToList();

                #endregion

                List<long> tVM_tasks_IDs = tVM.tasks.Select(p => p.ID).ToList();
                foreach (var task_ID in taskWithoutDateFilterAndHavePayments_IDs)
                {
                    if (!tVM_tasks_IDs.Contains(task_ID))
                    {
                        task addTask = tasksCopy.Where(p => p.ID == task_ID).First();
                        addTask.includeByPayment = true;
                        tVM.tasks.Add(addTask);
                    }
                }

                // заполнить суммы
                #region
                foreach (var _task in tVM.tasks)
                {
                    if (!_task.rightsEditDelete)
                        _task.sumPlanned = 0;

                    // все платежи
                    IQueryable<payment> payments = paymentRepo
                        .FindAll(p => p.task_ID == _task.ID 
                            && p.task.project.owner_ID == currentUserID).AsQueryable();

                    // платежи за период
                    IQueryable<payment> paymentsPeriod = payments;

                    if (tVM.planStartFilterFrom != null)
                    {
                        DateTime planStartFrom = (DateTime) tVM.planStartFilterFrom;
                        paymentsPeriod = paymentsPeriod.Where(p => p.timestamp > planStartFrom);
                    }

                    if (tVM.planStartFilterTo != null)
                    {
                        DateTime planStartTo = tools.getDayEndForDateTime((DateTime)tVM.planStartFilterTo);
                        paymentsPeriod = paymentsPeriod.Where(p => p.timestamp < planStartTo);
                    }

                    // все суммы по задачам
                    _task.sumInTotal = payments.Where(p => p.sumIn != null).Sum(p => (int)p.sumIn);
                    _task.sumOutTotal = payments.Where(p => p.sumOut != null).Sum(p => (int)p.sumOut);

                    // суммы за период по задачам
                    _task.sumInPeriod = paymentsPeriod.Where(p => p.sumIn != null).Sum(p => (int)p.sumIn);
                    _task.sumOutPeriod = paymentsPeriod.Where(p => p.sumOut != null).Sum(p => (int)p.sumOut);

                    // к оплате?
                    if (_task.taskStatus == taskStatuses.Выполнена.GetHashCode() && (_task.sumPlanned ?? 0) > _task.sumInTotal)
                    { tVM.sumPay += ((_task.sumPlanned ?? 0) - _task.sumInTotal); }

                }

                #endregion

            }

            #endregion

            tVM.tasks.AddRange(periodicalTasks); // вернем периодические задачи (если они были в запросе) после фильтра дат
            tVM.tasks = tVM.tasks.Distinct().ToList();

            // в заключение: фильтр "к оплате" !!!
            if (tVM.isToPay_filter)
                tVM.tasks = tVM.tasks
                    .Where(p => p.sumPlanned > p.sumInTotal 
                        && p.taskStatus == taskStatuses.Выполнена.GetHashCode()).ToList();

            // подсчитать ИТОГО
            tVM.sumTotal = tVM.tasks.Sum(p => p.sumPlanned ?? 0);

            tVM.sumInPeriod = tVM.tasks.Sum(p => p.sumInPeriod); 
            tVM.sumOutPeriod = tVM.tasks.Sum(p => p.sumOutPeriod);

            tVM.sumInTotal = tVM.tasks.Sum(p => p.sumInTotal); 
            tVM.sumOutTotal = tVM.tasks.Sum(p => p.sumOutTotal);
        }

        // добавить в модель словарь - имена пользователей
        public void addUserNamesToTasksViewModel(ref tasksViewModel tVM)
        {
            // администраторы
            List<long> userIDs = tVM.owners.Where(p => p.Value != "0")
                .Select(p => Convert.ToInt64(p.Value)).ToList();
            
            // заказчики
            userIDs.AddRange(tVM.customers.Where(p => p.Value != "0")
                .Select(p => Convert.ToInt64(p.Value)).ToList());

            // исполнители
            userIDs.AddRange(tVM.performers.Where(p => p.Value != "0")
                .Select(p => Convert.ToInt64(p.Value)).ToList());

            tVM.userNames = new _Repository<user>().FindAll(p => userIDs.Contains(p.ID))
                .Select(p => new SelectListItem
                {
                    Text = p.surName + " " + p.firstName,
                    Value = p.ID.ToString()
                }).ToList();
        }

        // формирование задач на основании периодических заданий
        public int formNewTasksOnPeriodicalTasks()
        {
            // получим список периодических задач, которые в действии
            // user _currentUser = new UserService().getCurrentUser();
            int qtyOfNewTasks = 0;
            List<user> users = new _Repository<user>()
                .FindAll(p => p.isEmailApproved && !p.isDeleted).ToList();

            foreach (var _currentUser in users)
            {
                DateTime? dt = _currentUser.formPeriodicalTaskWas;
                // если сегодня еще обработку не запускали - запустим!
                #region

                if (dt == null || dt < DateTime.Now)
                {
                    using (_Repository<user> userRepo = new _Repository<user>())
                    {
                        user _user = userRepo.FindAll(p => p.ID == _currentUser.ID).First();
                        _user.formPeriodicalTaskWas = DateTime.Today;

                        try
                        {
                            userRepo.Save();
                        }
                        catch (Exception ex)
                        {
                            ExceptionUtility.LogException(logger, ex,
                                "public void formNewTasksOnPeriodicalTasks()",
                                "Ошибка сохранения _user: " + _user.ID);
                        }
                    }

                    using (_Repository<task> taskRepo = new _Repository<task>())
                    {
                        // найдем включенные периодические задачи для текущего пользователя
                        List<task> periodicalTasks = taskRepo
                            .FindAll(p => p.taskStatus == taskStatuses.Периодическая.GetHashCode()
                            && p.periodicalIsOn && p.project.owner_ID == _currentUser.ID).ToList();

                        int dayOfWeek = DateTime.Today.DayOfWeek.GetHashCode();

                        foreach (var periodicalTaskItem in periodicalTasks)
                        {
                            task _addTask = null;
                            int addDays = 0;
                            // определим дату, когда запланировано событие
                            if (periodicalTaskItem.periodicalEveryWeekDay != null)
                            {
                                // ... которые запускаются каждый заданный день недели
                                int todayWeekDayHash = DateTime.Today.DayOfWeek.GetHashCode();

                                // сколько надо дней добавить к текущей дате?
                                addDays = (7 + ((int)periodicalTaskItem.periodicalEveryWeekDay) - todayWeekDayHash) % 7;

                            }
                            else if (periodicalTaskItem.periodicalEveryMonthDay != null)
                            {
                                // ... которые запускаются каждый заданный день месяца
                                // сколько надо дней добавить к текущей дате?

                                // дней в текущем месяце
                                int daysInCurrentMonth = DateTime.DaysInMonth(DateTime.Today.Year, DateTime.Today.Month);

                                addDays = (daysInCurrentMonth + ((int)periodicalTaskItem.periodicalEveryMonthDay) - DateTime.Today.Day) % daysInCurrentMonth;


                            }

                            _addTask = taskRepo.FindAll(p => p.parent_ID == periodicalTaskItem.ID
                                    && p.planStart == DateTime.Today.AddDays(addDays)).FirstOrDefault();

                            // если такой задачи нет - добавим
                            if (_addTask == null)
                            {
                                qtyOfNewTasks++;
                                _addTask = new task();

                                // копируем свойства
                                _addTask.parent_ID = periodicalTaskItem.ID;
                                _addTask.project_ID = periodicalTaskItem.project_ID;
                                _addTask.performer_ID = periodicalTaskItem.performer_ID;
                                _addTask.priority = periodicalTaskItem.priority;
                                _addTask.taskName = "[Создано " + periodicalTaskItem.ID + "] " + periodicalTaskItem.taskName;
                                _addTask.taskDescription = periodicalTaskItem.taskDescription;
                                _addTask.sumPlanned = periodicalTaskItem.sumPlanned;
                                _addTask.sumFact = periodicalTaskItem.sumFact;
                                _addTask.taskStatus = taskStatuses.Новая.GetHashCode();
                                _addTask.planStart = DateTime.Today.AddDays(addDays);

                                taskRepo.InsertInstance(_addTask);
                                try
                                {
                                    taskRepo.Save();
                                }
                                catch (Exception ex)
                                {
                                    ExceptionUtility.LogException(logger, ex,
                                        "public void formNewTasksOnPeriodicalTasks()",
                                        "Ошибка добавления задачи на основании периодической periodicalTaskItem.ID: " + periodicalTaskItem.ID);
                                }
                            }



                        }
                    }
                }

                #endregion

            }
            return qtyOfNewTasks;

        }

        // Method - Deserialize Class XML
        public List<itemKeywords> DeserializeMethodsKeywords(byte[] xmlByteData)
        {
            List<itemKeywords> itemsKeywords = new List<itemKeywords>();

            try
            {
                XmlSerializer ds = new XmlSerializer(typeof(List<itemKeywords>));
                MemoryStream memoryStream = new MemoryStream(xmlByteData);

                itemsKeywords = (List<itemKeywords>)ds.Deserialize((memoryStream));

            }
            catch (Exception ex)
            {
                ExceptionUtility.LogException(
                    logger, ex, "public List<itemKeywords> Deserialize(byte[] xmlByteData)",
                    "ошибка десериализации");
            }

            return itemsKeywords;
        }

        // Method - Deserialize Class XML
        public methodInfo DeserializeMethodInfo(byte[] xmlByteData)
        {
            methodInfo _methodInfo = new methodInfo();

            try
            {
                XmlSerializer ds = new XmlSerializer(typeof(methodInfo));
                MemoryStream memoryStream = new MemoryStream(xmlByteData);

                _methodInfo = (methodInfo)ds.Deserialize((memoryStream));

            }
            catch (Exception ex)
            {
                ExceptionUtility.LogException(
                    logger, ex, "public List<itemKeywords> Deserialize(byte[] xmlByteData)",
                    "ошибка десериализации");
            }

            return _methodInfo;
        }

        // получить из каталога краткую информациб по методу
        public methodInfo getMethodInfoFromPlanerka(long method_ID)
        { 
            string strURL = WebConfigurationManager.AppSettings["URL_getMethodInfo"];
            HttpWebRequest GetRequest = (HttpWebRequest)WebRequest.Create(strURL + method_ID);
            methodInfo _methodInfo = new methodInfo();
            try
            {
                //Get response from server
                HttpWebResponse response = (HttpWebResponse)GetRequest.GetResponse();

                // Get the stream associated with the response.
                Stream receiveStream = response.GetResponseStream();
                StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                string tempString = readStream.ReadToEnd();
                byte[] requestByte = Encoding.UTF8.GetBytes(tempString);

                // Deserialize
                _methodInfo = DeserializeMethodInfo(requestByte);
            }
            catch { }

            if (!String.IsNullOrEmpty(_methodInfo.methodNailPath))
                _methodInfo.methodNailPath = WebConfigurationManager.AppSettings["URL_getMethodNailPath"] + _methodInfo.methodNailPath;

            return _methodInfo;
        }

        // сохранить статус задачи
        public object saveTaskStatus(long task_ID, int taskStatus)
        {
            _Repository<task> taskRepo = new _Repository<task>();
            task _task = taskRepo.All().Where(p => p.ID == task_ID).FirstOrDefault();

            string message = "", result = "";

            if (_task != null)
            {
                _task.taskStatus = taskStatus;
                result = taskStatus.ToString();
                try
                {
                    taskRepo.Save();
                    message = "Записали данные";
                }
                catch (Exception ex)
                {
                    ExceptionUtility.LogException(logger, ex,
                        "[HttpPost] saveTaskStatus(long task_ID, int taskStatus)",
                        "Ошибка сохранения метки");
                }

            }
            else
                message = "Нет такого заказа!";

            var jsonData = new
            {
                Result = result,
                Message = message
            };

            return jsonData;
        }

    }
}
